/*
 *  Copyright (C) 2010 Glauco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.dao.hibernate;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.balisunrise.annotation.FilterType;
import org.balisunrise.common.Criterion;
import org.balisunrise.common.Query;
import org.balisunrise.common.DAO;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

/**
 *
 * @author Glauco
 * @version 1.0.0.0
 */
public class HDAO implements DAO<Session> {

    private Session openedSession;
    private boolean isOpenedSessionOpen;

    public HDAO() {
        isOpenedSessionOpen = false;
    }
    
    public Session getSession(){
        if (isOpenedSessionOpen)
            return openedSession;
        return HibernateUtil.getSessionFactory().openSession();
    }

    public void openSession(){
        openedSession = getSession();
        isOpenedSessionOpen = true;
    }

    public void closeSession(){
        if (isOpenedSessionOpen)
            openedSession.close();
        isOpenedSessionOpen = false;
    }

    private void closeIfCan(Session session){
        if (isOpenedSessionOpen)
            return;
        session.close();
    }

    public Serializable getId(Object entity) {

        Serializable id = null;

        Session session = getSession();

        try{
            id = session.getIdentifier(entity);
        }catch(Exception ex){
            System.out.println("*****************");
            System.out.println(ex.toString());
            System.out.println("*****************");
        }

        closeIfCan(session);

        return id;
    }

    public void save(Object entity){

        Session session = getSession();

        try{
            session.beginTransaction();
            session.saveOrUpdate(entity);
            session.getTransaction().commit();
        } catch(Exception ex){
            System.out.println("*****************");
            System.out.println(ex.toString());
            //ex.printStackTrace();
            System.out.println("*****************");
        }
        closeIfCan(session);
    }
    
    public Object read(Class c, Serializable key){

        if (c == null)
            System.out.println("HDAO.read: Class is null");
        if (key == null)
            System.out.println("HDAO.read: key is null");

        Session session = getSession();

        try{
            Object loaded = session.get(c, key);
            closeIfCan(session);
            return loaded;
        }catch(Exception ex){
            System.out.println("*****************");
            System.out.println(ex.toString());
            //ex.printStackTrace();
            System.out.println("*****************");
        }

        closeIfCan(session);
        return null;
    }
    
    public void delete(Object entity){

        Session session = getSession();

        try{
            session.beginTransaction();
            session.delete(entity);
            session.getTransaction().commit();
        }catch(Exception ex){
            System.out.println("*****************");
            System.out.println(ex.toString());
            System.out.println("*****************");
        }
        closeIfCan(session);
    }

    public List list(Class entityClass){

        if(null == entityClass)
            return null;
        
        List list = null;
        Session session = getSession();
        try{
            Criteria criteria = session.createCriteria(entityClass);
            list = criteria.list();
        }catch(Exception ex){
            System.out.println("*****************");
            System.out.println(ex.toString());
            System.out.println("*****************");
        }
        closeIfCan(session);
        return list;
    }

    public List list(Query query){
        if (query == null)
            return null;

        int aliasNumber = 1;
        List list = null;

        Session session = getSession();
        try{
            // cria Criteria
            Criteria criteria = session.createCriteria(query.getEntityClass());

            // adiciona ordem
            for (String o : query.getOrders()) {
                criteria.addOrder(Order.asc(o));
            }

            // adiciona resultado inicial
            if (query.getFirstResult() > 0)
                criteria.setFirstResult(query.getFirstResult());

            // adiciona maximo de resultados
            if (query.getMaxResults() > 0)
                criteria.setMaxResults(query.getMaxResults());

            // mapa de alias já criados
            Map<String,String> aliasMap = new HashMap();

            // para cada condição
            for (Criterion c : query.getCriterions()) {

                if (c == null)
                    continue;

                if(c.isEmptyForAll() && c.getValue() == null &&
                        (c.getFilterType() != FilterType.BETWEEN ||
                         c.getOtherValue() == null))
                    continue;

                // nome do campo que será usada para criar o criterion
                String propertyName = c.getFieldName();

                // Alias do property name
                String alias = null;

                // verifica os preNames para criar o alias
                if(c.getPreNames() != null)
                    for (String preName : c.getPreNames()) {

                        if(preName == null)
                            continue;

                        String realPreName = preName;

                        if(alias != null)
                            realPreName = alias + "." + realPreName;

                        if(aliasMap.containsKey(realPreName))
                            alias = aliasMap.get(realPreName);
                        else{
                            alias = makeAlias(aliasNumber);
                            aliasNumber++;
                            aliasMap.put(realPreName, alias);
                            criteria.createAlias(realPreName, alias);
                        }
                    }

                // se foi atribuido algum alias, inclui na frente
                if (alias != null)
                    propertyName = alias + "." + propertyName;

                org.hibernate.criterion.Criterion criterion =
                        makeCriterion(propertyName,
                                      c.getFilterType(),
                                      c.getValue(),
                                      c.getOtherValue());

                if(criterion != null)
                    criteria.add(criterion);
            }

            // cria resultados
            list = criteria.list();

        }catch(Exception ex){
            System.out.println("*****************");
            System.out.println(ex.toString());
            System.out.println("*****************");
        }

        closeIfCan(session);

        return list;
    }

    private String makeAlias(int alias){
        return "T" + alias;
    }

    private org.hibernate.criterion.Criterion makeCriterion(
            String propertyName, FilterType type,
            Object value, Object otherValue){

        if (type == FilterType.EQUALS)
            return Restrictions.eq(propertyName, value);
        else if (type == FilterType.GREATHER)
            return Restrictions.gt(propertyName, value);
        else if (type == FilterType.GREATHER_EQUALS)
            return Restrictions.ge(propertyName , value);
        else if (type == FilterType.LESS)
            return Restrictions.lt(propertyName, value);
        else if (type == FilterType.LESS_EQUALS)
            return Restrictions.le(propertyName, value);
        else if (type == FilterType.ILIKE)
            return Restrictions.ilike(propertyName, "%" + value + "%");
        else if (type == FilterType.BETWEEN)
            return Restrictions.between(propertyName, value, otherValue);
        else
            return null;
    }
}

